﻿using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Reflection;
using System.Text;
using Xom.Attributes;
using Xom.Elements;

namespace Xom.Engine
{
    public abstract class XomEngine
    {
        protected readonly ConcurrentDictionary<Type, IList<MappedPropertyInfo>> _typeProperties;

        protected XomEngine()
        {
            _typeProperties = new ConcurrentDictionary<Type, IList<MappedPropertyInfo>>();
        }

        protected object CreateNewXomElement(Type objectType)
        {
            object newObject = Activator.CreateInstance(objectType);
            IXomElement element = newObject as IXomElement;
            //todo create Interface not found exception
            if (element == null) throw new InternalBufferOverflowException();

            return element;
        }

        protected object ConvertXmlValueToObject(string value, Type targetType)
        {
            Type t = Nullable.GetUnderlyingType(targetType) ?? targetType;
            object newValue = null;

            try
            {
                if (targetType == typeof(string))
                {
                    newValue = (value == null) ? null : Convert.ChangeType(value, t, CultureInfo.InvariantCulture);
                }
                else
                {
                    if (t.BaseType == typeof(Enum))
                    {
                        try
                        {
                            newValue = Enum.Parse(t, value);
                        }
                        catch (ArgumentException e)
                        {
                            //xml value is not a element of the enum
                            return null;
                        }
                    }
                    else
                    {
                        newValue = (string.IsNullOrEmpty(value))
                                       ? null
                                       : Convert.ChangeType(value, t, CultureInfo.InvariantCulture);
                    }
                }
            }
            catch (Exception e)
            {
                if (e is FormatException | e is OverflowException)
                {
                    Trace.TraceWarning("Can't convert xml value \"" + value + "\" to " + targetType + "!");
                    if (t == targetType) newValue = Activator.CreateInstance(targetType);
                }
                else
                {
                    throw;
                }
            }

            return newValue;
        }

        protected string ConvertObjectValueToString(object value)
        {
            if (value == null) return null;

            Type t = Nullable.GetUnderlyingType(value.GetType()) ?? value.GetType();
            string xmlValue = (string)Convert.ChangeType(value, typeof(string), CultureInfo.InvariantCulture);

            //special double to string handling necessary? do it there...
            /*if (t == typeof(double))
            {
                xmlValue = ((double)value).ToString(CultureInfo.InvariantCulture);
            }*/

            //convert True and False values to lower
            if (t == typeof(bool))
            {
                if (!string.IsNullOrEmpty(xmlValue)) xmlValue = xmlValue.ToLower();
            }

            return xmlValue;
        }

        protected IList<MappedPropertyInfo> GetOrAddTypeProperties(Type objectType)
        {
            if (_typeProperties.ContainsKey(objectType)) return _typeProperties[objectType];

            IList<MappedPropertyInfo> mappedPropertyInfos = new List<MappedPropertyInfo>();
            PropertyInfo[] properties = objectType.GetProperties();

            foreach (PropertyInfo propertyInfo in properties)
            {
                //get XomBinding attribute
                object[] attributes = propertyInfo.GetCustomAttributes(typeof(XomBinding), true);
                if (attributes.Count() != 1) continue;
                XomBinding binding = attributes[0] as XomBinding;
                if (binding == null) continue;

                mappedPropertyInfos.Add(new MappedPropertyInfo { Binding = binding, Info = propertyInfo });
            }

            //order list by sequence index and name
            IEnumerable<MappedPropertyInfo> orderedList = mappedPropertyInfos.OrderBy(m => m.Binding.SequenceIndex).ThenBy(n => n.Binding.XmlName);
            mappedPropertyInfos = orderedList.ToList();

            _typeProperties.TryAdd(objectType, mappedPropertyInfos);

            return mappedPropertyInfos;
        }

        protected XomBinding GetPropertyBinding(Type objectType, string properyName)
        {
            IList<MappedPropertyInfo> propertyInfos = GetOrAddTypeProperties(objectType);
            foreach (MappedPropertyInfo property in propertyInfos)
            {
                if (property.Info.Name == properyName) return property.Binding;
            }

            return null;
        }
    }

    //todo description
    public class MappedPropertyInfo
    {
        public PropertyInfo Info { get; set; }

        public XomBinding Binding { get; set; }
    }
}
